package de.pass.ch.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.HashMap;

import de.pass.ch.assign.Assignment;
import de.pass.ch.datastr.EdgeList;
import de.pass.ch.datastr.PQueueNode;
import de.pass.ch.datastr.PQueueNodeDynQuery;
import de.pass.ch.datastr.PQueueNodeLocalSearch;
import de.pass.ch.graph.UpdateableGraph;
import de.pass.ch.graph.edge.CompleteEdge;
import de.pass.ch.graph.edge.Edge;
import de.pass.ch.graph.edge.SimpleEdge;
import de.pass.ch.graph.node.SearchNode;
import de.pass.ch.pt.IStreetProfile;
import de.pass.ch.util.Constants;
import de.pass.ch.util.Util;

import net.sourceforge.sizeof.SizeOf;

public class GraphIO {

	private static EdgeList edges;

	public static void main(String[] args) {

		Edge[] edges = readCorrectEdgesFromFile("dummyGraph");

		for (int i = 0; i < edges.length; i++) {
			System.out.println(edges[i]);
		}

		System.out.println("theoretisch: "
				+ (edges.length * SizeOf.deepSizeOf(edges[0])) + " byte");

		System.out.println(SizeOf.deepSizeOf(edges) + " byte");

		SimpleEdge e = new SimpleEdge(2, 4, true, false);
		Edge edge = new Edge(0, 0, (byte) 0, false, false);
		CompleteEdge cEdge = new CompleteEdge(2, 3, 5, false, true, false);
		SearchNode sn = new SearchNode(546546);

		System.out.println("SimpleEdge: " + SizeOf.deepSizeOf(e) + " byte");
		System.out.println("SearchNode: " + SizeOf.deepSizeOf(sn) + " byte");
		System.out.println("Edge: " + SizeOf.deepSizeOf(edge) + " byte");
		System.out.println("CompleteEdge: " + SizeOf.deepSizeOf(cEdge)
				+ " byte");

		EdgeList list = new EdgeList();
		System.out.println("EdgeList: " + SizeOf.deepSizeOf(list) + " byte");

		System.out.println(Runtime.getRuntime().availableProcessors()
				+ " CPU-Kerne gefunden");

		PQueueNode pqNode = new PQueueNode();
		PQueueNodeDynQuery pqDQ = new PQueueNodeDynQuery();
		PQueueNodeLocalSearch pqLS = new PQueueNodeLocalSearch();
		System.out
				.println("PQueueNode: " + SizeOf.deepSizeOf(pqNode) + " byte");
		System.out.println("PQueueNodeDynQuery: " + SizeOf.deepSizeOf(pqDQ)
				+ " byte");
		System.out.println("PQueueNodeLocalSearch: " + SizeOf.deepSizeOf(pqLS)
				+ " byte");

		System.gc();

		BitSet bitset = new BitSet(12000000);
		System.out.println("BitSet(12000000): " + SizeOf.deepSizeOf(bitset)
				+ " byte");
		bitset = null;
		System.gc();

		HashMap<Integer, Assignment[]> ass = new HashMap<Integer, Assignment[]>();
		System.out.println("HashMap<Integer, Assignment[]>: "
				+ SizeOf.deepSizeOf(ass) + " byte");
		for (int j = 0; j < 1000; j++) {
			Assignment[] assArr = new Assignment[2];
			assArr[0] = new Assignment();
			assArr[1] = new Assignment();
			ass.put(j, assArr);
		}
		System.out.println("HashMap<Integer, Assignment[]>: "
				+ SizeOf.deepSizeOf(ass) + " byte");

	}

	/**
	 * 
	 * @param graphFile
	 * @param hwyFile
	 */
	public static UpdateableGraph importUpdatableGraph(String graphFile,
			String hwyFile, IStreetProfile streetProfile, int mode) {

		System.out.println("------   ddsg eingelesen   ------");
		int noOfNodes = readEdgesFromFile(graphFile, streetProfile, mode);
		System.out.println("------   removeDuplicates   ------");
		removeDuplicates();
		System.out.println("------   removeDuplicates fertig   ------");

		int[] nodeLevels = new int[noOfNodes];
		for (int i = 0; i < nodeLevels.length; i++) {
			nodeLevels[i] = noOfNodes;
		}

		UpdateableGraph graph = new UpdateableGraph(edges, nodeLevels);

		edges = null;
		System.gc();

		return graph;
	}

	/**
	 * Removes parallel edges and self loops from the input and writes the
	 * unique edges to the output.
	 */
	private static void removeDuplicates() {
		// sort the edge list first by source, then by target and weight
		Comparator<CompleteEdge> comp = new CompleteEdgeComparator();
		System.out.println("Sortieren der kompletten Kanten-Liste");
		long start = System.currentTimeMillis();
		edges.sort(0, edges.size(), comp);
		System.out.println("edges.sort took "
				+ (System.currentTimeMillis() - start) + " ms");
		comp = null;

		int selfLoops = 0;
		int parallelEdges = 0;
		int weightDependsOnDir = 0;
		int mergeTwoOneWays = 0;
		int downgradeToOneWay = 0;
		int zeroWeight = 0;

		CompleteEdge lastEdge = new CompleteEdge(0, 0, 0, false, true, true);

		int i = 0;
		// scan the original edge list
		for (CompleteEdge currentEdge = edges.get(0); currentEdge != null; currentEdge = currentEdge
				.getNext(), i++) {
			if (currentEdge.getWeight() <= 0) {
				zeroWeight++;
				// edges.remove(currentEdge);
				// i--;
				// continue;
			}
			if (currentEdge.getSource() == currentEdge.getTarget()) {
				// skip self loop
				selfLoops++;
				edges.remove(currentEdge);
				i--;
				// VERBOSE2(cout << "self-loop " << currentEdge << " skipped."
				// << endl);
			} else {
				if ((currentEdge.getSource() != lastEdge.getSource())
						|| (currentEdge.getTarget() != lastEdge.getTarget())) {

					lastEdge = currentEdge;
					// add unique edge to the new edge list (that doesn't
					// contain duplicates)
				} else {
					assert (i > 0);
					// Does the current edge point in another direction
					// than the previous edge ?
					if (((currentEdge.isDirected((byte) 0)) && (!lastEdge
							.isDirected((byte) 0)))
							|| ((currentEdge.isDirected((byte) 1)) && (!lastEdge
									.isDirected((byte) 1)))) {

						if (currentEdge.getWeight() == lastEdge.getWeight()) {
							// same weight -> sorting order implies that both
							// edges are one-way and complementary -> don't add
							// the current edge, but make the last edge two-way
							assert ((currentEdge.isDirected((byte) 0) != lastEdge
									.isDirected((byte) 0))
									&& (currentEdge.isDirected((byte) 1) != lastEdge
											.isDirected((byte) 1)) && (currentEdge
										.isDirected((byte) 0) != currentEdge
									.isDirected((byte) 1)));

							// edges.get(i - 1).makeTwoWay();
							lastEdge.makeTwoWay();
							edges.remove(currentEdge);
							i--;
							mergeTwoOneWays++;
							continue;
						}

						if (currentEdge.isBidirected()) {
							// Since the last (one-way) edge is shorter,
							// the current (two-way) edge is only useful
							// for the opposite direction
							assert (currentEdge.getWeight() > lastEdge
									.getWeight());
							if (lastEdge.isDirected((byte) 0))
								currentEdge.makeOneWay((byte) 1);
							if (lastEdge.isDirected((byte) 1))
								currentEdge.makeOneWay((byte) 0);
							downgradeToOneWay++;
						}

						// set both direction flags to indicate that no further
						// edge is needed for the current s-t-pair
						// lastEdge.makeTwoWay();

						// add unique edge to the new edge list
						weightDependsOnDir++;
					} else {
						// skip parallel edge
						// note: since parallel edges are sorted by weight,
						// it is ensured that the shorter one "survives"
						parallelEdges++;
						edges.remove(currentEdge);
						i--;
					}
				}
			}
		}

		System.gc();

		System.out.println("selfLoos: " + selfLoops);
		System.out.println("parallelEdges: " + parallelEdges);
		System.out.println("weightDependsOnDir: " + weightDependsOnDir);
		System.out.println("mergeTwoOneWays: " + mergeTwoOneWays);
		System.out.println("downgradeToOneWay: " + downgradeToOneWay);
		System.out.println("zeroWeight: " + zeroWeight);
	}

	private static int readHeaderFromFile(String graphFile) {
		int res = 0;

		if (!graphFile.endsWith(".ddsg"))
			graphFile += ".ddsg";

		File f = new File(graphFile);
		if (!f.exists()) {
			return 0;
		}
		Reader r;
		BufferedReader reader;
		try {

			r = new InputStreamReader(new FileInputStream(f));
			reader = new BufferedReader(r);

			String line = null;
			if ((line = reader.readLine()) != null) {
				if (!"d".equals(line.trim())) {
					reader.close();
					return 0;
				}
			} else {
				reader.close();
				return 0;
			}
			if ((line = reader.readLine()) != null) {
				String[] split = line.trim().split(" ");
				if (!(split.length == 2)) {
					reader.close();
					return 0;
				}
				res = Integer.parseInt(split[0].trim());
			} else {
				reader.close();
				return 0;
			}

			reader.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
		return res;
	}

	private static int readEdgesFromFile(String graphFile,
			IStreetProfile streetProfile, int mode) {

		int noOfNodes = readHeaderFromFile(graphFile);
		int noOfEdges = 0;

		if (!graphFile.endsWith(".ddsg"))
			graphFile += ".ddsg";

		File f = new File(graphFile);
		if (!f.exists()) {
			return 0;
		}

		FileInputStream fileIn = null;
		Reader r = null;
		BufferedReader reader = null;

		try {

			fileIn = new FileInputStream(f);
			r = new InputStreamReader(fileIn);
			reader = new BufferedReader(r);

			String line = null;
			reader.readLine();

			if ((line = reader.readLine()) != null) {
				String[] split = line.trim().split(" ");
				if (!(split.length == 2)) {
					reader.close();
					return 0;
				}
				noOfEdges = Integer.parseInt(split[1].trim());
			} else {
				reader.close();
				return 0;
			}

			edges = new EdgeList();

			int source = 0;
			int target = 0;
			int km = 0;
			int direction = 0;
			short type = 0;
			short factor = 0;
			boolean forward = true;
			boolean backward = true;

			int weight = 0;

			int i = 0;
			while ((line = reader.readLine()) != null && i < noOfEdges) {
				String[] split = line.trim().split(" ");
				if (split.length < 4) {
					reader.close();
					return 0;
				}
				forward = true;
				backward = true;
				source = Integer.parseInt(split[0].trim());
				target = Integer.parseInt(split[1].trim());
				km = Integer.parseInt(split[2].trim());
				direction = Integer.parseInt(split[3].trim());
				
				if (split.length >= 6) {
					type = Short.parseShort(split[4].trim());
					factor = Short.parseShort(split[5].trim());
				}

				if (mode == Constants.DISTANCE_OPT) {
					weight = km;
				} else {
					weight = Util.calculateDuration(km, type, factor,
							streetProfile);
				}

				if (direction == 1)
					backward = false;
				if (direction == 2)
					forward = false;

				edges.add(new CompleteEdge(source, target, weight, false,
						forward, backward));
				edges.add(new CompleteEdge(target, source, weight, false,
						backward, forward));

				i++;
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null)
					reader.close();
				if (r != null)
					r.close();
				if (fileIn != null)
					fileIn.close();
			} catch (Exception e) {
			}
		}

		return noOfNodes;
	}

	private static CompleteEdge[] readCorrectEdgesFromFile(String graphFile) {
		CompleteEdge[] res = null;

		int m = 0;

		if (!graphFile.endsWith(".ddsg"))
			graphFile += ".ddsg";

		File f = new File(graphFile);
		if (!f.exists()) {
			return null;
		}
		Reader r;
		BufferedReader reader;
		try {

			r = new InputStreamReader(new FileInputStream(f));
			reader = new BufferedReader(r);

			String line = null;
			if ((line = reader.readLine()) != null) {
				if (!"d".equals(line.trim())) {
					reader.close();
					return null;
				}
			} else {
				reader.close();
				return null;
			}
			if ((line = reader.readLine()) != null) {
				String[] split = line.trim().split(" ");
				if (!(split.length == 2)) {
					reader.close();
					return null;
				}
				m = Integer.parseInt(split[1].trim());
			} else {
				reader.close();
				return null;
			}

			res = new CompleteEdge[(m * 2) + 1];

			int source = 0;
			int target = 0;
			int weight = 0;
			int direction = 0;
			boolean forward = true;
			boolean backward = true;

			int i = 0;
			while ((line = reader.readLine()) != null && i < m) {
				String[] split = line.trim().split(" ");
				if (!(split.length == 4)) {
					reader.close();
					return null;
				}
				forward = true;
				backward = true;
				source = Integer.parseInt(split[0].trim());
				target = Integer.parseInt(split[1].trim());
				weight = Integer.parseInt(split[2].trim());
				direction = Integer.parseInt(split[3].trim());

				if (direction == 1)
					backward = false;
				if (direction == 2)
					forward = false;

				res[i * 2] = new CompleteEdge(source, target, weight, false,
						forward, backward);
				res[(i * 2) + 1] = new CompleteEdge(target, source, weight,
						false, backward, forward);

				i++;
			}

			res[res.length - 1] = new CompleteEdge(Integer.MAX_VALUE,
					Integer.MAX_VALUE, Integer.MAX_VALUE, false, false, false);

			reader.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		Arrays.sort(res);

		return res;
	}

	private static class CompleteEdgeComparator implements
			Comparator<CompleteEdge> {

		@Override
		public int compare(CompleteEdge e1, CompleteEdge e2) {
			if (e1.getSource() == e2.getSource()) {
				if (e1.getTarget() == e2.getTarget()) {
					if (e1.getWeight() == e2.getWeight()) {
						if (e1.isBidirected() && !e2.isBidirected()) {
							return -1;
						}
						return 1;
					}
					return ((Integer) e1.getWeight()).compareTo((Integer) e2
							.getWeight());
				}
				return ((Integer) e1.getTarget()).compareTo((Integer) e2
						.getTarget());
			}
			return ((Integer) e1.getSource()).compareTo((Integer) e2
					.getSource());
		}

	}

}
